需求

html渲染是一个相对常见的需求,在web前端做起来比较容易。Java中很多组价也支持直接填入html原始数据进行格式化渲染。但有时我们需要将html转换为格式化文本,举例来说,满足如下条件

  • 反转html实体,如&
  • 去除所有标签
  • 除换行之外的所有格式全都去除

实现

目前市面上并没有一步实现该需求的方案,最为接近可行的方案是Jsoup:https://github.com/jhy/jsoup;它能够将html文本转换为dom树,并能提取其中文本和元素。

我们的实现逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private fun Document.format(): String {
return StringBuffer().apply { [email protected]().format(this) }.toString()
}

private fun Element.format(sb: StringBuffer) {
if (this.childNodeSize() == 0) return
this.childNodes().forEach {
when (it) {
is TextNode -> {
sb.append(it.wholeText.trim())
}
is Element -> when (it.tagName()) {
"div", "p", "ul" -> {
it.format(sb)
}
"li" -> {
sb.append("- ")
it.format(sb)
}
"span", "pre" -> {
it.format(sb)
sb.append(" ")
}
"br" -> Unit
"a" -> {
sb.append("<${it.attr("href")}>")
it.format(sb)
}
else -> println(it)
}
is Comment -> Unit
else -> println(it)
}
// 如果下一个标签是这些,则需要换行
if (it.nextSibling() is Element &&
(it.nextSibling() as Element).tagName() in listOf("div", "p", "ul", "li", "a", "br")
)
sb.append("\n")
}
}

fun main() {
val html = """
这是你的html温拌
""".trimIndent()
println(Jsoup.parse(html).body())
println(Jsoup.parse(html).format())
}

我们输入如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<div>
18/47/284
</div>
<div>
<br>
</div>
<div>
<br>
</div>
<div>
北京&nbsp;
</div>
<div>
李诞
</div>
<div>
李丹
<br>
</div>
<div>
<p><br></p>
<p>成都</p>
<p>尺子</p>
<p><br></p>
<p>上海&nbsp;</p>
<p>池子</p>
<p>格桑酒店</p>
<p><br></p>
<p>杭州&nbsp;</p>
<p><br></p>
<div>
<p>深圳</p>
<p>建国</p>
<p><br></p>
<p>西安</p>
<p>李雪琴</p>
<p><br></p>
<p>武汉</p>
<p>王建国</p>
<p><br><span></span></p>
</div>
</div>

得到的输出,完全满足需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/47/284

北京
李诞
李丹

成都
尺子

上海
池子
格桑酒店

杭州

深圳
建国

说明

这里说明编写中的注意事项

  • dom结构

    明白dom结构很重要,最开始我就搞错了文本本身也是一个节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- 评论 -->
    文本1
    <div>
    <div>
    文本1
    </div>
    </div>
    <p>
    文本2
    </p>

    节点结构如下

    1
    2
    3
    4
    5
    6
    7
    Comment
    TextNode
    Element(div)
    - Element(div)
    - TextNode
    Element(p)
    - TextNode
  • 换行符的处理

    不能直接在块标签后增加换行符,因为块标签会嵌套,从早造成多余的换行

    取而代之的,应该看下一个兄弟元素是否是块标签或换行标签

    1
    2
    3
    4
    5
    6
    7
    8
    <div>
    <div>
    文本1
    </div>
    </div>
    <p>
    文本2
    </p>

    如上文本肯定想要得到如下结果

    1
    2
    文本1
    文本2

    如果按块标签换行,则会得到如下结果,显然是不正确的。

    1
    2
    3
    文本1

    文本2

    “下一个相邻元素”,jsoup中有提供nextSibling()方法进行获取

  • 超链接的处理

    超链接提供getAttr()获取href属性。

  • 评论标签的处理

    评论标签有专门的节点类型——Comment,可以直接识别以忽略。

留言

2021-07-07

⬆︎TOP